﻿using NUnit.Framework;
using Shouldly;
using System;
using DotNetCommon.Extensions;
using Newtonsoft.Json.Linq;

namespace DotNetCommon.Test.Extensions
{
    [TestFixture]
    public class ObjectTests_To
    {
        /// <summary>
        /// null 或 DBNull 转其他
        /// </summary>
        [Test]
        public void TestNullToOther()
        {
            Person person = null;
            var st = person.To<IPerson>();
            st.ShouldBeNull();
            var st2 = person.To<int?>();
            st.ShouldBeNull();

            DBNull dbnull = DBNull.Value;
            var st3 = dbnull.To<IPerson>();
            st3.ShouldBeNull();
            var st4 = dbnull.To<int?>();
            st4.ShouldBeNull();
        }

        /// <summary>
        /// 测试DateOnly 和 TimeOnly
        /// </summary>
        [Test]
        public void TestDateOnlyTimeOnly()
        {
            //字符串转DateOnly TimeOnly
            "2020-01-01".To<DateOnly>().ShouldBe(new DateOnly(2020, 01, 01));
            "13:01:56.123".To<TimeOnly>().ShouldBe(new TimeOnly(13, 01, 56, 123));
            //DateTime => DateOnly TimeOnly
            "2020-01-01 13:01:56.123".To<DateTime>().To<DateOnly>().ShouldBe(new DateOnly(2020, 01, 01));
            "2020-01-01 13:01:56.123".To<DateTime>().To<TimeOnly>().ShouldBe(new TimeOnly(13, 01, 56, 123));
            //DateTimeOffset => DateOnly TimeOnly
            "2020-01-01 13:01:56.123 +08:00".To<DateTimeOffset>().To<DateOnly>().ShouldBe(new DateOnly(2020, 01, 01));
            "2020-01-01 13:01:56.123 +08:00".To<DateTimeOffset>().To<TimeOnly>().ShouldBe(new TimeOnly(13, 01, 56, 123));
            //TimeSpan => TimeOnly
            "13:01:56.123".To<TimeSpan>().To<TimeOnly>().ShouldBe(new TimeOnly(13, 01, 56, 123));

            //DateOnly TimeOnly => string
            "2020-01-01".To<DateOnly>().To<string>("yyyy-MM-dd").ShouldBe("2020-01-01");
            "13:01:56.123".To<TimeOnly>().To<string>("HH:mm:ss.fff").ShouldBe("13:01:56.123");

            //TimeOnly => TimeSpan
            "13:01:56.123".To<TimeOnly>().To<TimeSpan>().ShouldBe(new TimeSpan(0, 13, 01, 56, 123));

            //DateOnly TimeOnly => DateTime DateTimeOffset
            "2020-01-01".To<DateOnly>().To<DateTime>().ShouldBe("2020-01-01".To<DateTime>("yyyy-MM-dd"));
            "2020-01-01".To<DateOnly>().To<DateTimeOffset>().ShouldBe("2020-01-01".To<DateTimeOffset>("yyyy-MM-dd"));
            "13:01:56.123".To<TimeOnly>().To<DateTime>().ShouldBe("1970-01-01 13:01:56.123".To<DateTime>());
            "13:01:56.123".To<TimeOnly>().To<DateTimeOffset>().ShouldBe("1970-01-01 13:01:56.123".To<DateTimeOffset>());
        }

        /// <summary>
        /// 字符串和guid互转
        /// </summary>
        [Test]
        public void TestString2Guid()
        {
            var guid = Guid.Parse("a66cc049-edee-426d-9aa2-dea498149b8d");
            //guid => string
            var str = guid.To<string>();
            str.ShouldBe("a66cc049-edee-426d-9aa2-dea498149b8d");
            str = guid.To<string>("N");
            str.ShouldBe("a66cc049edee426d9aa2dea498149b8d");
            str = guid.To<string>("D");
            str.ShouldBe("a66cc049-edee-426d-9aa2-dea498149b8d");
            str = guid.To<string>("B");
            str.ShouldBe("{a66cc049-edee-426d-9aa2-dea498149b8d}");
            str = guid.To<string>("P");
            str.ShouldBe("(a66cc049-edee-426d-9aa2-dea498149b8d)");
            str = guid.To<string>("X");
            str.ShouldBe("{0xa66cc049,0xedee,0x426d,{0x9a,0xa2,0xde,0xa4,0x98,0x14,0x9b,0x8d}}");

            //string => guid
            "a66cc049-edee-426d-9aa2-dea498149b8d".To<Guid>().ShouldBe(guid);
            "a66cc049edee426d9aa2dea498149b8d".To<Guid>().ShouldBe(guid);
            "a66cc049-edee-426d-9aa2-dea498149b8d".To<Guid>().ShouldBe(guid);
            "{a66cc049-edee-426d-9aa2-dea498149b8d}".To<Guid>().ShouldBe(guid);
            "(a66cc049-edee-426d-9aa2-dea498149b8d)".To<Guid>().ShouldBe(guid);
            "{0xa66cc049,0xedee,0x426d,{0x9a,0xa2,0xde,0xa4,0x98,0x14,0x9b,0x8d}}".To<Guid>().ShouldBe(guid);



        }

        /// <summary>
        /// 字符串转日期
        /// </summary>
        [Test]
        public void TestString2DateTime()
        {
            //字符串->DateTime
            "2020-01-01".To<DateTime>().ShouldBe(DateTime.Parse("2020-01-01"));
            "2020-01-01 02:03:04.123".To<DateTime>().ShouldBe(DateTime.Parse("2020-01-01 02:03:04.123"));
            "2020-01-02T01:08:07.123Z".To<DateTime>().ShouldBe(DateTime.Parse("2020-01-02T01:08:07.123Z"));
            "2020-01-02 09:08:07 +08:00".To<DateTime>().ShouldBe(DateTime.Parse("2020-01-02 09:08:07 +08:00"));


            string str = null;
            str.To<DateTime?>().ShouldBe(null);

            //字符串->DateTimeOffset
            "2020-01-01".To<DateTimeOffset>().ShouldBe(DateTimeOffset.Parse("2020-01-01"));
            "2020-01-01 02:03:04.123".To<DateTimeOffset>().ShouldBe(DateTimeOffset.Parse("2020-01-01 02:03:04.123"));
            "2020-01-02T01:08:07.123Z".To<DateTimeOffset>().ShouldBe(DateTimeOffset.Parse("2020-01-02T01:08:07.123Z"));
            "2020-01-02 09:08:07 +08:00".To<DateTimeOffset>().ShouldBe(DateTimeOffset.Parse("2020-01-02 09:08:07 +08:00"));

            //字符串->DateTime 指定格式
            "2021-05-09 23:16:03.503".To<DateTime>("yyyy-MM-dd HH:mm:ss.fff").ShouldBe(DateTime.Parse("2021-05-09 23:16:03.503"));
            "2021年的05月09日啊".To<DateTime>("yyyy年的MM月dd日啊").ShouldBe(DateTime.Parse("2021-05-09"));
            "2020-01-02 09:08:07.123 +08:00".To<DateTime>("yyyy-MM-dd HH:mm:ss.fff zzz").ShouldBe(DateTime.Parse("2020-01-02 09:08:07.123 +08:00"));
            "2020-01-02T01:08:07Z".To<DateTime>("yyyy-MM-ddThh:mm:ssZ").ShouldBe(DateTime.Parse("2020-01-02T01:08:07Z"));

            //字符串->DateTimeOffset 指定格式
            "2021-05-09 23:16:03.503".To<DateTimeOffset>("yyyy-MM-dd HH:mm:ss.fff").ShouldBe(DateTimeOffset.Parse("2021-05-09 23:16:03.503"));
            "2021年的05月09日啊".To<DateTimeOffset>("yyyy年的MM月dd日啊").ShouldBe(DateTimeOffset.Parse("2021-05-09"));
            "2020-01-02 09:08:07.123 +08:00".To<DateTimeOffset>("yyyy-MM-dd HH:mm:ss.fff zzz").ShouldBe(DateTimeOffset.Parse("2020-01-02 09:08:07.123 +08:00"));
            "2020-01-02T01:08:07Z".To<DateTimeOffset>("yyyy-MM-ddThh:mm:ssZ").ShouldBe(DateTimeOffset.Parse("2020-01-02T01:08:07Z"));
        }

        /// <summary>
        /// 字符串转bool
        /// </summary>
        [Test]
        public void TestStringToBool()
        {
            //字符串转bool
            //"true"、"ok"、"yes"、"1"转为true，其他转为false，不区分大小写
            var str = "true";
            str.To<bool>().ShouldBeTrue();
            str = "True";
            str.To<bool>().ShouldBeTrue();
            str = "false";
            str.To<bool>().ShouldBeFalse();
            str = "False";
            str.To<bool>().ShouldBeFalse();

            str = "Ok";
            str.To<bool>().ShouldBeTrue();
            str = "notOk";
            str.To<bool>().ShouldBeFalse();

            str = "yes";
            str.To<bool>().ShouldBeTrue();
            str = "no";
            str.To<bool>().ShouldBeFalse();

            str = "1";
            str.To<bool>().ShouldBeTrue();
            str = "0";
            str.To<bool>().ShouldBeFalse();

            str = null;
            str.To<bool?>().ShouldBeNull();
        }

        [Test]
        public void TestDateTimeToString()
        {
            var str = new DateTime(2020, 01, 02).To<string>("yyyy-MM");
            str.ShouldBe("2020-01");

            str = new DateTimeOffset(2020, 01, 02, 03, 04, 05, TimeSpan.FromHours(8)).To<string>("yyyy-MM");
            str.ShouldBe("2020-01");
        }

        /// <summary>
        /// 枚举转字符串
        /// </summary>
        [Test]
        public void TestEnumToString()
        {
            //枚举转字符串
            EnumState.Active.To<string>().ShouldBe("Active");
            (EnumState.Active | EnumState.Close).To<string>().ShouldBe("Close, Active");
        }

        /// <summary>
        /// 字符串转数字
        /// </summary>
        [Test]
        public void TestStringToNumber()
        {
            //字符串转数字
            var str = "1.2";
            str.To<double>().ShouldBe(1.2);
            str = null;
            str.To<double?>().ShouldBe(null);
            str.ToWithDefault<int>(0).ShouldBe(0);
            str = "12";
            str.To<int>().ShouldBe(12);
        }

        /// <summary>
        /// 字符串转枚举
        /// </summary>
        [Test]
        public void TestStringToEnum()
        {
            //字符串转枚举
            var str = "Close";
            str.To<EnumState>().ShouldBe(EnumState.Close);
            str = "CLOSE,active";
            var state = str.To<EnumState>();
            state.Contains(EnumState.Close).ShouldBeTrue();
            state.Contains(EnumState.Active).ShouldBeTrue();
            state.Contains(EnumState.Open).ShouldBeFalse();

            state = "open,close,active".To<EnumState>();
            state.Contains(EnumState.Close | EnumState.Active).ShouldBeTrue();

            state = "open".To<EnumState>();
            state.ContainsAny(EnumState.Open | EnumState.Active).ShouldBeTrue();

            //空字符串转枚举
            "".To<EnumState?>().ShouldBe(null);
            //非对应的转枚举
            "dadjajsdjknasd".To<EnumState?>().ShouldBe(null);
            //json数组转枚举
            """["active","close"]""".To<EnumState?>().ShouldBe(EnumState.Close | EnumState.Active);
            """[2,4]""".To<EnumState?>().ShouldBe(EnumState.Close | EnumState.Active);
            """["2","4"]""".To<EnumState?>().ShouldBe(EnumState.Close | EnumState.Active);
        }

        /// <summary>
        /// 数字字符串转枚举
        /// </summary>
        [Test]
        public void TestNumberStringToEnum()
        {
            //字符串转枚举
            "1,2".To<EnumState>().ShouldBe(EnumState.Close | EnumState.Open);
            "1,2".To<EnumState?>().ShouldBe(EnumState.Close | EnumState.Open);
            "4".To<EnumState>().ShouldBe(EnumState.Active);
            "7".To<EnumState>().ShouldBe(EnumState.Active | EnumState.Close | EnumState.Open);

            //不是枚举内的数字转枚举, 有什么意义吗?
            "9".To<EnumState?>().ShouldBe((EnumState)9);
            "-1".To<EnumState?>().ShouldBe((EnumState)(-1));
        }

        /// <summary>
        /// 枚举转数字
        /// </summary>
        [Test]
        public void TestEnumToNumber()
        {
            EnumState.Open.To<int?>().ShouldBe(1);
            EnumState.Close.To<int>().ShouldBe(2);
            (EnumState.Close | EnumState.Open).To<int>().ShouldBe(3);

            //int ii = 5;
            //ulong ul = (ulong)ii;
            var res = (ulong)(EnumState.Close | EnumState.Open);
            Assert.That(res == 3);
            res = (EnumState.Close | EnumState.Open).To<ulong>();
            Assert.That(res == 3);
        }

        /// <summary>
        /// 数字转枚举
        /// </summary>
        [Test]
        public void TestNumberToEnum()
        {
            1.To<EnumState>().ShouldBe(EnumState.Open);
            2.To<EnumState>().ShouldBe(EnumState.Close);
            3.To<EnumState>().ShouldBe(EnumState.Open | EnumState.Close);
            4.To<EnumState>().ShouldBe(EnumState.Active);
            7.To<EnumState>().ShouldBe(EnumState.Open | EnumState.Close | EnumState.Active);
        }

        /// <summary>
        /// 数字转数字
        /// </summary>
        [Test]
        public void TestNumberToNumber()
        {
            //decimal转double
            1.88m.To<double>().ShouldBe(1.88d);

            //double转decimal
            1.88d.To<decimal>().ShouldBe(1.88m);

            //int转double
            20.To<double>().ShouldBe(20.0d);

            //double转int
            1.88d.To<int>().ShouldBe(2);
        }

        [Flags]
        public enum EnumState { Open = 1, Close = 2, Active = 4 }

        [Test]
        public void TestFail()
        {
            var str = "lp1";
            str.ToWithDefault<int>(default(int)).ShouldBe(default(int));
            str.ToWithDefault<int>(2).ShouldBe(2);
        }

        [Test]
        public void TestClass()
        {
            var person = new Person();
            Shouldly.Should.Throw(() => person.To<Student>(), typeof(InvalidCastException));
        }

        public interface IPerson { }
        public class Person { }

        public class Student { }

        [Test]
        public void TestFromJson()
        {
            "1".ToObject<object>().To<int>().ShouldBe(1);
            "true".ToObject<object>().To<bool>().ShouldBe(true);
            "null".ToObject<object>().To<bool?>().ShouldBe(null);
            var s = "{}".ToObject<object>().To<Student>();
            s.ToJson().ShouldBe("{}");
        }

        [Test]
        public void TestFronNewtonSoft()
        {
            //from Newtonsoft
            var obj = new { Name = "tom", Age = 20, Birth = DateTime.Parse("1995-01-02") };
            var jobject = obj.ToJson().ToObject<JObject>();
            jobject["Age"].To<int>().ShouldBe(20);
            jobject["Name"].To<string>().ShouldBe("tom");
            jobject["Birth"].To<DateTime>().ShouldBe(DateTime.Parse("1995-01-02"));

            var str = "{\"Time\":\"1992-02-03\"}";
            str.ToObject<JObject>()["Time"].GetType().Name.ShouldBe("JValue");
            str.ToObject<JObject>()["Time"].To<DateTime>().ShouldBe(DateTime.Parse("1992-02-03"));

            //to Newtonsoft
            jobject = obj.ToJson().To<JObject>();
            jobject["Name"].ToString().ShouldBe("tom");
            jobject["Age"].Value<int>().ShouldBe(20);

            //no Newtonsoft
            var s = 1.To<EnumState>();
            var dt = DateTime.Now.ToString().To<DateTime>();
        }

        class PersonJsonTest { public int Id { get; set; } public string Name { get; set; } }
        [Flags]
        enum EnumTest { A = 1, B = 2, C = 4 }
        [Test]
        public void TestJsonNodeToObjct()
        {
            var jsonNode = new { Id = 1, Name = "jack" }.ToJsonNode();
            var obj = jsonNode.To<PersonJsonTest>();
            obj.Id.ShouldBe(1);
            obj.Name.ShouldBe("jack");
        }
        [Test]
        public void TestJsonValueTo()
        {
            var jsonNode = new { Id = 1, Name = "jack" }.ToDictionary()
                .SetFluent("IdNull", null)
                .SetFluent("Flag", "1,2")
                .ToJsonNode();

            var num = jsonNode["Id"].To<int>();
            num.ShouldBe(1);

            var numNull = jsonNode["IdNull"].To<int?>();
            numNull.ShouldBeNull();

            var str = jsonNode["Name"].To<string>();
            str.ShouldBe("jack");

            var enu = jsonNode["Flag"].To<EnumTest>();
            enu.ShouldBe(EnumTest.A | EnumTest.B);

            var p = "1".ToObject<object>().To<int>();
            p.ShouldBe(1);

            var p2 = "null".ToObject<object>().To<int?>();
            p2.ShouldBeNull();
        }
    }
}
